package user;

import java.math.BigInteger;
import java.security.SignatureException;
import org.apache.axis2.AxisFault;

import service.arbitrator.ArbitratorDetails;

import common.*;

public class CUser {

    public String getCommitmentsAsString() {
        return AnonMath.getCommitmentsAsString(this.commitments, this.coefficients);
    }

    public String getPolynomialAsString() {
        return AnonMath.getgetPolynomialAsString(this.coefficients);
    }

    public String getinterpolationPointsAString() {
        return AnonMath.getinterpolationPointsAString(this.interpolationPoints);
    }
    //ID's
    public int tmpIDFromCA = -1;
    public byte[] tmpIDSignature = null;
    public Integer[] desiredArbitratorsIDs = null;
    public Integer[] serverAcceptableArbitrator = null;
    public Integer[] userAcceptableArbitrators;// = new Integer[]{0, 1, 2, 3, 4};
    // Signatures
    public byte[][] arbitratorsPrivateKeysEncoding = null;
    public SignatureCreator signatureCreator = null;
    public SignedData[] commitmentsSignatures = null;
    // MATH
    public Integer[] coefficients;
    public Integer secret;
    public Point[] interpolationPoints;
    public BigInteger[] commitments;
    // services
    UserRPCClient serverWebService = null;
    UserRPCClient arbitratorWebService = null;
    UserRPCClient CAWebService = null;

    public CUser() {
        setServerService();
        setArbitratorService();
        setCAWebService();
/*
        setSecret(987);
        createPolynom();
        createInterpolationPoints();
        getTmpIDFromCA();
        signTmpID();
        getCommitmentsFromCA();



        ArbitratorDetails[] ads;

        ads = this.getAllAvailableArbitrators();
        System.out.println("CUser: got " + ads.length + " arbitrators to choose from");

        // Basically, now the user can choose which of the possible arbitrators he wants to use 
        this.userAcceptableArbitrators = new Integer[]{0, 1, 2, 3, 4};
        boolean ss = startSessionWithServer(userAcceptableArbitrators).getActionSucceeded();

        if (ss) {
            getPrivateKeysFromArbitrators();
        }
        System.out.println("CUSER DONE");

        ///////////////////////////////////////////////////////////////
		/*
         * this is for constructor
         * rest is just for debuging
         */
        ///////////////////////////////////////////////////////////////
		/*
        for (Point share:this.interpolationPoints){
        System.out.println(AnonMath.verifyShare(share,commitments));
        }		
        
        System.out.println("Interpolated secret: "+AnonMath.interpolate(interpolationPoints));
         */

    }

    private void setCAWebService() {
        CAWebService = new UserRPCClient(Settings.CA_URI);
    }

    private void setArbitratorService() {
        arbitratorWebService = new UserRPCClient(Settings.ARBITRATOR_URI);
    }

    private void setServerService() {
        serverWebService = new UserRPCClient(Settings.SERVER_URI);
    }

    /**
     * Set clients secret
     * @param secret- the new secret 
     */
    public void setSecret(int secret) {
        this.secret = secret;

    }

    /**
     * Get an unique ID from the CA
     */
    public void getTmpIDFromCA() {
        this.tmpIDFromCA = (Integer) this.CAWebService.wsCall(
                new Class[]{Integer.class}, Settings.CA_NS, "getTemporaryId", null)[0];
    }

    /**
     * Sign temporary ID by CA	 
     */
    public void signTmpID() {
        this.tmpIDSignature = (byte[]) this.CAWebService.wsCall(new Class[]{byte[].class}, Settings.CA_NS, "sign", new Object[]{AnonMath.intToByteArray(this.tmpIDFromCA)})[0];
    }

    public void createPolynom() {
        this.coefficients = AnonMath.createPolynom(this.secret);
    }

    /**
     * create interpolation points (at first will be: 1,2,..,numOfShares)
     * create interpolation values (shares)
     */
    public void createInterpolationPoints() {
        this.interpolationPoints = AnonMath.createInterpolationPoints(this.coefficients);
    }

    public void getCommitmentsFromCA() {
        Class[] returnTypes = new Class[]{String[].class};
        String namespaceURI = Settings.CA_NS;
        String methodName = "getCommitments";
        Object[] mArgs = new Object[]{this.coefficients, this.secret};
        String[] tmpCommitments = (String[]) (this.CAWebService.wsCall(returnTypes, namespaceURI, methodName, mArgs)[0]);
        this.commitments = AnonMath.convertStringToBigInteger(tmpCommitments);

        returnTypes = new Class[]{SignedData[].class};
        methodName = "getSignedCommitments";
        SignedData[] sc = (SignedData[]) (this.CAWebService.wsCall(returnTypes, namespaceURI, methodName, mArgs)[0]);
        /***
         * EDIT: no use to convert SignedCommitments[] to byte[][]. as we'll be sending them out again.
        byte[][] tmpSigs = new byte[sc.length][];
        for (i = 0; i < sc.length; i++) {
        tmpSigs[i] = sc[i].getSignature();
        }
         ***/
        this.commitmentsSignatures = sc;
    }

    public ArbitratorDetails[] getAllAvailableArbitrators() {
        Class[] returnTypes = new Class[]{ArbitratorDetails[].class};
        String namespaceURI = Settings.ARBITRATOR_NS;
        String methodName = "getArbitrators";
        Object[] mArgs = (Object[]) null;
        return (ArbitratorDetails[]) (this.arbitratorWebService.wsCall(returnTypes, namespaceURI, methodName, mArgs)[0]);
    }

    /**
     * Start a new session with the server.
     * Start negotiation over arbitrators
     * Need to send at least Settings.threshold * 2 arbitrators
     * @param acceptableArbitrators - Arbitrators acceptable by the user    
     */
    public ServerResponse startSessionWithServer(Integer[] acceptableArbitrators) {
        Class[] returnTypes = new Class[]{ServerResponse.class};
        String namespaceURI = Settings.SERVER_NS;
        String methodName = "initSession";
        Object[] mArgs = new Object[]{this.tmpIDFromCA, this.tmpIDSignature, acceptableArbitrators};
        ServerResponse sr = (ServerResponse) (this.serverWebService.wsCall(returnTypes, namespaceURI, methodName, mArgs)[0]);
        if (sr.getActionSucceeded()) {
            // sr.getIntData() will contain an array of agreed arbitrators
            desiredArbitratorsIDs =sr.getIntData();
            System.out.println(this.desiredArbitratorsIDs.length);
        }
        return sr;
    }

    public void getPrivateKeysFromArbitrators() {
        Point share;
        //System.out.println(this.desiredArbitratorsIDs.length);
        this.arbitratorsPrivateKeysEncoding = new byte[this.desiredArbitratorsIDs.length][];
        for (int i = 0; i < this.desiredArbitratorsIDs.length; i++) {
            share = this.interpolationPoints[i % this.interpolationPoints.length];
            this.arbitratorsPrivateKeysEncoding[i] = startSessionWithArbitrator(this.desiredArbitratorsIDs[i], share);
        }
    }

    private byte[] startSessionWithArbitrator(Integer arbitratorId, Point share) {
        //	(Integer arbitratorID, Integer tmpUserID,  byte[] IDSignature, String[] commitmentsAsString, byte[][] commitmentsSignatures , Point share);
        byte[] privateKey;
        String[] commitmentsAsString = AnonMath.convertBigIntegerToString(this.commitments);
        Class[] returnTypes = new Class[]{byte[].class};
        String namespaceURI = Settings.ARBITRATOR_NS;
        String methodName = "initUserSession";
        Object[] mArgs = new Object[]{arbitratorId, this.tmpIDFromCA, this.tmpIDSignature,
            commitmentsAsString, this.commitmentsSignatures, share};
        privateKey = (byte[]) this.arbitratorWebService.wsCall(returnTypes, namespaceURI, methodName, mArgs)[0];
        return privateKey;
    }

    /**
     * Send a message to the server. 
     * Includes client temporary ID, message itself and a signature of the message.
     * @param message- message to send
     * @throws AxisFault
     * @throws SignatureException
     */
    public ServerResponse postMessage(String message) throws SignatureException {
        SignedData[] signedMessage = new SignedData[this.desiredArbitratorsIDs.length];
        for (int i = 0; i < this.desiredArbitratorsIDs.length; i++) {
            signedMessage[i] = new SignedData();
            signedMessage[i].setSignature(sign(message.getBytes(), this.arbitratorsPrivateKeysEncoding[i]));
        }
        Class[] returnTypes = new Class[]{ServerResponse.class};
        String namespaceURI = Settings.SERVER_NS;
        String methodName = "postMessage";
        Object[] mArgs = new Object[]{this.tmpIDFromCA, this.tmpIDSignature, message, signedMessage};
        ServerResponse sr;
        sr = (ServerResponse) this.serverWebService.wsCall(returnTypes, namespaceURI, methodName, mArgs)[0];
        return sr;
    }

    public Message[] getMessagesFromServer() {
        Class[] returnTypes = new Class[]{Message[].class};
        String namespaceURI = Settings.SERVER_NS;
        Message[] msgsFromSrv = (Message[]) (serverWebService.wsCall(returnTypes, namespaceURI, "getMessages", null)[0]);
        return msgsFromSrv;
    }

    public static void debugPrint(String s) {
        if (Settings.debugMode) {
            System.out.println("Debug: " + s);
        }
    }

    private byte[] sign(byte[] byteArrToSign, byte[] privateKey) throws SignatureException {
        this.signatureCreator = new SignatureCreator(privateKey);
        return this.signatureCreator.sign(byteArrToSign);
    }
}
